/* Sokoban
 * HIT3243
 * Authors: Mike Blackney, Ben Ingarfield, Kurniawan
 */

#include "ai.h"
#include "pawn.h"
#include "dxf.h"
#include "level.h"
#include "registrar.h"
#include "xfileentity.h"

using namespace dxf;

// Constant values for object physics:
#define SPEED_DELTA 4.0f
#define GRAVITY_DELTA 0.8f
#define UP_JUMP_DELTA 8.0f
#define DOWN_JUMP_DELTA 10.0f
#define TERMINAL_VELOCITY 75.0f

// Time-saving macros; pawn is not moving if all vectors are zero:
#define NOT_MOVING ((dx==0.0f)&&(dy==0.0f))
// Pawn is moving if any vectors are non-zero:
#define MOVING ((dx!=0.0f)||(dy!=0.0f))

// Pawn is in the air if they are higher than a tile:
#define IN_AIR (pawn->z<pawn->x+pawn->y-1)

// Minimum time we'd like frames to appear on screen:
#define MIN_FRAME_TIME 0.15f

// Depth at which a snake ball will hatch:
#define SNAKE_HATCH_DEPTH 5.0f

// Time it takes for a model to rotate to a new facing:
#define ROTATE_TIME 0.25f

// Time we wait before checking for another turn press:
#define TURN_COUNTDOWN 0.5f


// Disc floating values:
#define DISC_FLOAT_TIME 1.5f
#define DISC_TARGET_X 0.0f
#define DISC_TARGET_Y 0.0f
#define DISC_TARGET_Z -3.0f

// Animation tags:
#define FACE_N 0
#define FACE_E 1
#define FACE_S 2
#define FACE_W 3

AiFunction::AiFunction( ) 
:	targetX(0.0F), targetY(0.0f),
	dx(0.0F), dy(0.0F),
	theta(0.0f), pushing(NULL) {
}

AiFunction::~AiFunction( ) {
	// Nothing to clean up.
}

void AiFunction::Position( float fx, float fy, float fz ) {
	targetX = fx;
	targetY = fy;
}

void AiFunction::Animate( Pawn *pawn, long flags ) {
	// No default animation, just cycle:
	if (pawn->model)
		pawn->model->SetAnimationSet( 0 );
}

void AiFunction::Collide( Pawn*, Pawn* ) {
	// Should never be called!
	assert(false);
}

void AiFunction::operator( )( Pawn *pawn, double fTime, float fElapsedTime ) {
	Ai( pawn, fTime, fElapsedTime );
}

void AiFunction::Reset( Pawn *pawn ) {
	dx = dy = 0.0f;
	targetX = pawn->x;
	targetY = pawn->y;
	//Animate( pawn, ANIMATE_STILL );
	pushing = NULL;
}

// Inherited Ai function.  Moves pawn based on delta vectors.
void AiFunction::Ai( Pawn *pawn, double fTime, float fElapsedTime ) {

	if ( MOVING ){
		// Set up the animation facing flags now while
		// we're still moving:
		static long animate_flags = 0;
		if (dy < 0) animate_flags = FACE_N;
		else if (dx > 0) animate_flags = FACE_E;
		else if (dy > 0) animate_flags = FACE_S;
		else if (dx < 0) animate_flags = FACE_W;

		// Move position:
		if ( (dx > 0) && (pawn->x < targetX)
		||   (dx < 0) && (pawn->x > targetX)
		||   (dy > 0) && (pawn->y < targetY)
		||   (dy < 0) && (pawn->y > targetY) ) {
			pawn->x += dx * fElapsedTime;
			pawn->y += dy * fElapsedTime;
			// If we're pushing anything, move it as well:
			if (pushing) {
				pushing->x += dx * fElapsedTime;
				pushing->y += dy * fElapsedTime;
			}
		}
		// Update vectors:
		else {
			pawn->x = targetX;
			pawn->y = targetY;
			dx = dy = 0.0f;
			Level::Instance( ).SetPlayerMoving( false );
			Level::Instance( ).RegisterMovement(pawn);
			// If we're pushing, do the same:
			if (pushing) {
				// No longer pushing:
				pushing->x = static_cast<float>(round(pushing->x));
				pushing->y = static_cast<float>(round(pushing->y));
				Level::Instance( ).RegisterMovement(pushing);
				pushing = NULL;
				// Check with the Level to see if we're done
				// with this level:
				Level::Instance( ).CheckLevelClear( );
			}
		}
		// Gravity:
		// If he's in the air above a tile, or off the map...
		/*
		if ( (pawn->z  < targetX + targetY - 1) || (
			( dz > 0 ) 
			&& ( (targetX < 0)
			  || (targetY < 0)
			  || (targetX + targetY >= MAX_LEVEL_WIDTH) )) ) {

			// Make him fall:
			dz += GRAVITY_DELTA;
			if (dz > TERMINAL_VELOCITY)
				dz = TERMINAL_VELOCITY;
		}
		else if (dz > 0) {
			// Otherwise, make him land on the surface if he's going down:
			dz = 0;
			pawn->z = targetX + targetY - 1;
		}
		*/
		//pawn->z += dz * fElapsedTime;

		// Do animation.
		// Are we going up or down, or did we just land?
		//if (dz < 0) animate_flags |= ANIMATE_AIR;
		//else if (dz > 0) animate_flags |= ANIMATE_FALL;
		//else animate_flags |= ANIMATE_LAND;
		// Now animate:
		if (Registrar::iDifficulty != 2)
			Animate(pawn,animate_flags);
	}

}

PlayerAi::PlayerAi( double fTime ) 
: AiFunction(), fTimeLanded(fTime) {
}

/* Player AI function.
 * Allows the player to press a key and move ninja if
 * he's stationary.  Otherwise, moves ninja in the
 * pressed direction.
 */
float PlayerAi::x = 0.0f;
float PlayerAi::y = 0.0f;
float PlayerAi::z = 0.0f;

void PlayerAi::Ai( Pawn *pawn, double fTime, float fElapsedTime ) {
	// Sanity check:
	assert(NULL != pawn);

	// Update AI position record so enemies can follow us:
	x = pawn->x;
	y = pawn->y;
	z = pawn->z;

	enum Facing {
		North, East, South, West
	};
	static int facing = North;
	static double turnCountdown = 0.0f;

	// Only allow movement if we're stationary:
	if ( NOT_MOVING ){

		// Work out the desired direction based on keypress:
		int desiredX = static_cast<int>(x), desiredY = static_cast<int>(y),
		// and make sure the player's not pressing more than one cursor key:
			numKeys = 0;

		// For normal modes, this is how camera works:
		if (Registrar::iDifficulty != 2) {

			// Check for keys:
			if (DXFCheckKeyboard(VK_UP) == BUTTON_DOWN) {
				desiredY = static_cast<int>(y) - 1;
				dy = -SPEED_DELTA;
				++numKeys;
			}
			if (DXFCheckKeyboard(VK_DOWN) == BUTTON_DOWN) {
				desiredY = static_cast<int>(y) + 1;
				dy =  SPEED_DELTA;
				++numKeys;
			}
			if (DXFCheckKeyboard(VK_LEFT) == BUTTON_DOWN) {
				desiredX = static_cast<int>(x) - 1;
				dx = -SPEED_DELTA;
				++numKeys;
			}
			if (DXFCheckKeyboard(VK_RIGHT) == BUTTON_DOWN) {
				desiredX = static_cast<int>(x) + 1;
				dx =  SPEED_DELTA;
				++numKeys;
			}
		}
		// Hard mode: FPS controls:
		else {
			// Get the camera position:
			D3DXVECTOR3 &CameraPos = Level::Instance( ).AccessDesiredCameraPos( );

			// Check for keys:
			// Forward
			if (DXFCheckKeyboard(VK_UP) == BUTTON_DOWN) {

				switch (facing) {
					case North:
						desiredY = static_cast<int>(y) - 1;
						dy = -SPEED_DELTA;
						break;
					case East:
						desiredX = static_cast<int>(x) + 1;
						dx = SPEED_DELTA;
						break;
					case South:
						desiredY = static_cast<int>(y) + 1;
						dy =  SPEED_DELTA;
						break;
					case West:
						desiredX = static_cast<int>(x) - 1;
						dx = -SPEED_DELTA;
						break;
					default:
						assert(false && "Strange facing!!");
				}
				++numKeys;
			}
			/*
			// Backward
			if (DXFCheckKeyboard(VK_DOWN) == BUTTON_DOWN) {
				desiredX = static_cast<int>(y) + 1;
				desiredY = static_cast<int>(y) + 1;
				dx = -SPEED_DELTA;
				dy =  SPEED_DELTA;
				++numKeys;
			}*/

			turnCountdown -= fElapsedTime;
			if (turnCountdown <= 0.0f) {
				// Turn left:
				if (DXFCheckKeyboard(VK_LEFT) == BUTTON_DOWN) {
					if (facing == North) facing = West;
					else --facing;
					turnCountdown = TURN_COUNTDOWN;
				}
				// Turn right:
				else if (DXFCheckKeyboard(VK_RIGHT) == BUTTON_DOWN) {
					if (facing == West) facing = North;
					else ++facing;
					turnCountdown = TURN_COUNTDOWN;
				}
			}

			CameraPos = D3DXVECTOR3( y * -150, z * -150, x * -150 );
			
			switch (facing) {
				case North:
					CameraPos += D3DXVECTOR3( -450, 300, 0 );
					break;
				case East:
					CameraPos += D3DXVECTOR3( 0, 300, 450 );
					break;
				case South:
					CameraPos += D3DXVECTOR3( 450, 300, 0 );
					break;
				case West:
					CameraPos += D3DXVECTOR3( 0, 300, -450 );
					break;
				default:
					assert(false && "Something strange going on with facing!");
					break;
			}
		}

		if (MOVING) {

			// If the desired tile can be moved into (i.e. it's empty,
			// AND if it has a crate, the crate can be pushed ):
			if ( Level::Instance( ).TileIsEmpty( desiredX, desiredY )
				// And we're not pressing more than one key:
				&& numKeys == 1) {
				// Then the player can go to the new target location:
				targetX = static_cast<float>(desiredX);
				targetY = static_cast<float>(desiredY);
				// Register movement for the player:
				Level::Instance( ).RegisterMovement(pawn);
			}
			// Otherwise, he might still be able to move if he's able
			// to push a crate out of the way:
			else if ( Level::Instance( ).CanPushCrate( desiredX, desiredY, pawn ) ) {
				pushing = Level::Instance( ).GetCrate( desiredX, desiredY );
				targetX = static_cast<float>(desiredX);
				targetY = static_cast<float>(desiredY);
				// Register movement for the player and the crate:
				Level::Instance( ).RegisterMovement(pawn);
				Level::Instance( ).RegisterMovement(pushing);
			}
			// Otherwise stay still, bro!
			else {
				dx = 0;
				dy = 0;
			}
		}

		// Now if we're moving, it's official.
		if (MOVING) {
			// If we're moving, update the no. of turns:
			++Registrar::iNumTurns;
			// If we're pushing, update the no. of pushes:
			if (pushing)
				++Registrar::iNumPushes;
			// And tell the level that we're now moving:
			if (numKeys) 
				Level::Instance( ).SetPlayerMoving( true );
		}

		/*
		// If the player just pressed a key:
		if (MOVING) {
			fTimeLanded = fTime;
		}
		*/

		// If we just landed or are about to jump,
		// we want to use the appropriate animation:
		if (Registrar::iDifficulty == 2)
			Animate(pawn,facing);
	
	}
	
	// Chain up to do physics:
	AiFunction::Ai( pawn, fTime, fElapsedTime );
}

void PlayerAi::Collide( Pawn*, Pawn* ) {
// This should never happen:
	assert(false);
}


void PlayerAi::Animate( Pawn *pawn, long flags ) {

	// This is where we animate the 3d model:
	if (pawn->model) {
		/*
		int frame = 0;
		if (flags & ANIMATE_STILL) frame = 3;
		if (flags & ANIMATE_JUMP) frame = 2;
		if (flags & ANIMATE_AIR) frame = 1;
		if (flags & ANIMATE_FALL) frame = 1;
		if (flags & ANIMATE_LAND) frame = 0;
		*/
		
		float intended_theta = 0;
		if (flags == FACE_N) intended_theta = 3 * D3DX_PI / 2;
		if (flags == FACE_E) intended_theta = 0 * D3DX_PI / 2;
		if (flags == FACE_S) intended_theta = 1 * D3DX_PI / 2;
		if (flags == FACE_W) intended_theta = 2 * D3DX_PI / 2;

		if ( abs(intended_theta - theta) < D3DX_PI / 64)
			theta = intended_theta;
		else if ( abs(intended_theta - theta) > D3DX_PI )
			if (theta > intended_theta) {
				theta += D3DX_PI / 8;
				if (theta > D3DX_PI * 2) theta = 0;
			} else {
				theta -= D3DX_PI / 8;
				if (theta < 0) theta = D3DX_PI * 2;
			}
		else
			if (theta > intended_theta) {
				theta -= D3DX_PI / 16;
			} else {
				theta += D3DX_PI / 16;
			}

		//theta = intended_theta;
		static D3DXVECTOR3 rotationaxis = D3DXVECTOR3(0,1,0);
		static D3DXMATRIX rotationmatrix;

		D3DXMatrixRotationAxis(&rotationmatrix, &rotationaxis, theta);
		
		pawn->model->SetWorldTransform(&rotationmatrix);
		//pawn->model->SetAnimationSet( frame );
		//pawn->model->SetAnimationSet( frame );

	}
}